Skip to content

docs: record EIP-2929 gas-estimation impact under v0.7.0 - #331

Open
crazywriter1 wants to merge 5 commits into
circlefin:mainfrom
crazywriter1:docs/eip2929-v070-gas-estimation
Open

docs: record EIP-2929 gas-estimation impact under v0.7.0#331
crazywriter1 wants to merge 5 commits into
circlefin:mainfrom
crazywriter1:docs/eip2929-v070-gas-estimation

Conversation

@crazywriter1

@crazywriter1 crazywriter1 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • CHANGELOG v0.7.0 linked to BREAKING_CHANGES.md#v070 for EIP-2929 precompile account-load gas impact, but that section had no entry.
  • Documents that NativeCoinAuthority mint/burn/transfer now charge EIP-2929 cold/warm account-access gas (ships with the v0.7.x binary; not hardfork-gated).
  • Notes CallFrom (Memo / Multicall3From) separately: different cold-access path, only live once Zero7 is active.
  • Also covers the related CallFrom cold-access Fix from the same release; re-estimate target is v0.7.1+ (no v0.7.0 tag).

Test plan

  • Confirm CHANGELOG.md EIP-2929 bullet still links to #v070
  • Confirm entry is under v0.7.0 For Node Operators
  • Confirm examples match account_load_cost call sites (NativeCoinAuthority), not CallFrom-only paths

@osr21

osr21 commented Sep 3, 2026

Copy link
Copy Markdown

Checked this against the repo. The premise holds, the placement is right, and the aggregation is better than the description claims. Four notes, one of which I'd treat as a correctness fix rather than a nit.

The dangling link is real, and this closes exactly one gap

CHANGELOG.md:182 promises something the target section never delivered:

- [EL] Apply EIP-2929 warm/cold pricing to precompile account loads;
  see [BREAKING_CHANGES.md](./BREAKING_CHANGES.md#v070) for the gas-estimation impact

I audited every BREAKING_CHANGES.md#... link in the changelog rather than just this one, since a partial fix would be worth knowing about. There are twelve, and only one dangled:

Anchor Links Resolves to content
#v070 4 (L146, L153, L179, L182) 3 of 4 — L146 → BC:101, L153 → BC:121, L179 → BC:110. L182 had nothing.
#v071 1 yes
#v072 4 yes
#v080 6 yes

So this is precisely targeted — it's the only gap of its kind in the file. The #v070 anchor itself is valid; GitHub slugifies ## [v0.7.0] to v070 by stripping the brackets and dots.

The aggregation is worth more than the description says

The note covers two separate changelog entries, both under ## [v0.7.0] (L133–L246):

  • L182 (Changes) — precompile account loads. Carries the #v070 link.
  • L217 (Fixes) — [EL] Charge EIP-2929 cold account access cost in `CallFrom` subcalls. Carries no link.

Folding L217 in is a real improvement rather than scope creep: it lives in a different subsection, has no pointer of its own, and is the same gas-schedule change from the caller's perspective. Worth saying so in the description, because as written it reads like a single-entry backfill.

I also went looking for a conflation in "`CallFrom` (Memo) subcalls" and found the opposite. contracts/src/memo/Memo.sol is documented as wrapping the callFrom precompile, and IMemo.sol spells out the EOA-only constraint (msg.sender of Memo must equal the precompile caller). Memo is the canonical caller of CallFrom, so naming it is the right concrete hook. Good choice.

1. The sub-bullet inverts the mechanism (worth fixing)

Precompile account access and CallFrom (Memo) subcalls charge EIP-2929 cold/warm account-access gas that v0.6.x did not.

Read plainly, "precompile account access" says that accessing the precompile is now cold/warm priced. That reading is wrong, and in a way a reader can't self-correct: EIP-2929 initializes accessed_addresses with tx.sender, tx.to, and the full precompile set, so precompile addresses are pre-warmed and never incur the 2600 cold charge on entry.

What actually changed is the other direction — account loads performed by the precompile. From crates/precompiles/src/helpers.rs:

/// Gas cost to load an account balance for stateful precompiles.
///
/// Applies EIP-2929 warm/cold pricing.
fn account_load_cost(is_cold: bool) -> u64 {
    if is_cold { COLD_ACCOUNT_ACCESS_COST } else { WARM_STORAGE_READ_COST }
}

transfer() charges it twice — once for the loaded from account, once for to — each keyed on that account's own is_cold flag.

This isn't pedantry, because the two readings imply different remediation. The wrong one suggests a flat ~2600 uplift per precompile call, which someone could paper over with a fixed bump. The real cost scales with how many distinct cold accounts the call touches, so a Memo subcall to a fresh recipient costs more than one to an address already in the access list — and that's the number an integrator needs to size a limit against.

The changelog's own phrasing, "precompile account loads", is already correct. Suggest mirroring it:

Account loads performed by Arc precompiles — including CallFrom (Memo) subcalls — now charge EIP-2929 cold/warm account-access gas that v0.6.x did not. Cost scales with the number of distinct cold accounts a call touches.

2. TokenMessenger can't be verified from this repo

The bullet cites "CCTP TokenMessenger calls" as an affected path. TokenMessenger appears nowhere in arc-node, and CCTP appears exactly once — in README prose listing Circle platform products, not as an integration point.

The claim may well be true of the deployed network, but BREAKING_CHANGES.md is the normative upgrade document for this repo, and an example that can't be checked against anything here will drift silently. Either drop it, or swap in a surface that does exist in-tree — Memo and Multicall3From are both under contracts/src/ and are already the precompile-adjacent paths you're describing.

3. Missing the [Tag] prefix

Every one of the 18 existing top-level bullets in BREAKING_CHANGES.md opens with a bracketed tag — **[CLI] …**, **[Config] …**, **[Format] …**. This is the only one without one, which will read as an oversight next to its neighbours.

None of the three existing tags honestly fits a gas-schedule change, so this probably warrants a new one — **[Gas]** or **[EVM]**. Flagging rather than prescribing, since adding to the taxonomy is a maintainer call.

4. The taxonomy referenced in the test plan doesn't exist

Confirm the new bullet is under v0.7.0 For Node Operators and does not touch v0.7.2 / application-developer taxonomy

There is no application-developer taxonomy — "Application Developer", "For Developers", and "Contract Developer" return zero matches across every markdown file in the repo. BREAKING_CHANGES.md has only For Node Operators and For Validators.

Raising it because the underlying tension is real: this content is squarely application-developer facing (re-estimate your gas, your hardcoded limits are too low), and there's no section that fits. Your placement is still the right one, and there's precedent — v0.8.0's [Format] JSON-RPC error-text bullet sits under For Node Operators while explicitly addressing "tooling that matches JSON-RPC error text". So the file already treats that section as the home for RPC-surface changes with a consumer-facing audience. Just worth not citing a section that isn't there.

One wording point

Consensus execution is unchanged for callers that already supply sufficient gas.

Gas accounting is consensus state — it lands in receipts and the state root — so as phrased this slightly undersells the change. What's actually unchanged is the transaction outcome. And since these precompiles are gated on the Zero7 hardfork, this rides a fork boundary rather than being a silent reprice, which is the more reassuring fact for an operator. Something like: "Transaction outcomes are unchanged for callers that already supply sufficient gas; the new pricing activates with the Zero7 hardfork."


Net: a real gap, found by following a link that promised something absent, and fixed in the right place. Item 1 I'd want corrected before merge since it misstates the mechanism; items 2–4 are polish.

Usual caveat — no cargo or rustc available here, so the Rust references above are source reading rather than execution.

@crazywriter1

Copy link
Copy Markdown
Contributor Author

Thanks for the review @osr21. Addressed review:

  1. Rewrote the mechanism bullet to match changelog phrasing — account loads performed by precompiles, with cost scaling by distinct cold accounts.
  2. Dropped TokenMessenger; in-tree paths only (Memo, Multicall3From, CallFrom).
  3. Prefixed with [EVM] and documented the tag in the taxonomy list.
  4. Left placement under For Node Operators (precedent with v0.8.0 RPC-surface notes). Updated the commit/PR description: this also covers the v0.7.0 CallFrom cold-access Fix (changelog L217), not only the L182 Changes entry.

@osr21

osr21 commented Sep 3, 2026

Copy link
Copy Markdown

Re-checked d6b31bf1. Three of the four are cleanly resolved. But one line I told you to add is wrong, and I need to retract it before this merges.

Retraction: my Zero7 sentence is incorrect

I suggested this, and you adopted it verbatim:

Transaction outcomes are unchanged for callers that already supply sufficient gas; the new pricing activates with the Zero7 hardfork.

The second clause is false. I asserted the hardfork gating from the fact that CallFrom is Zero7-gated, without checking whether the pricing was. It isn't.

account_load_cost has no hardfork parameter at all (crates/precompiles/src/helpers.rs:146). Its four call sites — in transfer (:396, :414), balance_incr (:470), and balance_decr (:517) — charge it unconditionally. Those helpers do receive hardfork_flags, which is what misled me, but the flags are used for Zero8 gating further down (:574, :608), never for the account load. Tellingly, balance_incr is called without any flags argument at all (native_coin_authority.rs:177).

The precompiles that use those helpers are not gated either. ArcPrecompileProvider::create_precompiles_map registers NATIVE_COIN_AUTHORITY, NATIVE_COIN_CONTROL, SYSTEM_ACCOUNTING, and PQ through a plain address match with no is_active check anywhere in the function.

And the repo proves it with a test. native_coin_authority.rs::account_load_cold_oog runs with:

let zero6_flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]);

— Zero7 deliberately absent — and asserts that a mint to a cold, pre-funded target OOGs at exactly COLD_ACCOUNT_ACCESS_COST while the same call to a pre-warmed target at the same budget succeeds. The cold-account surcharge is demonstrably live with Zero7 inactive.

The only Zero7 gate is build_subcall_registry (crates/evm/src/evm.rs:1672), which registers CALL_FROM_ADDRESS with AllowedCallers::Only({MEMO_ADDRESS, MULTICALL3_FROM_ADDRESS}). So Zero7 controls whether the CallFrom precompile exists, not how account loads are priced.

Why this is worth a force-push rather than a follow-up. The sentence tells an operator there is a fork boundary to coordinate around — that costs won't move until Zero7 activates. For the NativeCoinAuthority paths the reprice ships with the binary: a v0.6.xv0.7.0 node upgrade changes gas costs immediately, with no fork signal to wait for. My wording actively encourages deferring the re-estimation this entry exists to demand. That inverts the document's purpose, and it's a worse error than the one I originally flagged.

Suggested replacement for that sub-bullet:

  • Re-estimate against a v0.7.0 (or later) node. Transaction outcomes are unchanged for callers that already supply sufficient gas. Note this is not gated behind a hardfork — the new pricing takes effect as soon as the node runs v0.7.0, so re-estimate at upgrade time rather than at the next fork. (CallFrom itself activates with Zero7, but that governs the precompile's availability, not the account-load pricing.)

Apologies for the churn — I should have traced the gating before recommending the clause.

The other three are correct

  • Mechanism bullet — now reads "Account loads performed by Arc precompiles," matching helpers.rs and the changelog's own phrasing. The "cost scales with the number of distinct cold accounts" clause is the part that makes it actionable. Resolved.
  • TokenMessenger → in-tree paths — I verified my own suggestion this time rather than assuming. Multicall3From is real (contracts/src/batch/Multicall3From.sol, IMulticall3From.sol, DeployMulticall3From.s.sol) and is one of exactly two addresses permitted to call CallFrom per the AllowedCallers set above. Memo is the other. So Memo, Multicall3From, CallFrom is precisely the right triple — those two are the complete caller set, not just examples.
  • [EVM] tag — added and documented. Good call preferring it over [Gas]; it generalizes to non-gas execution-cost changes.

Two small things

1. The taxonomy list is alphabetical. [CLI], [Config], [Format] — the new entry was appended after [Format], which breaks it. [EVM] sorts between [Config] and [Format].

2. The heading and first sub-bullet now say the same thing twice.

  • [EVM] EIP-2929 warm/cold pricing now applies to account loads performed by Arc precompiles.
    • Account loads performed by Arc precompiles — including CallFrom (Memo) subcalls — now charge EIP-2929 cold/warm account-access gas that v0.6.x did not.

Every other multi-line entry in the file uses the heading as the claim and the sub-bullets as non-overlapping detail. The two v0.7.0 [Config]/[CLI] entries both use an explicit shape worth borrowing here, since this is exactly a version-to-version behavior change:

- Old (`v0.6.0`): …
- New (`v0.7.0`): …

That would let the first sub-bullet carry the v0.6.xv0.7.0 delta and the cold-account scaling without restating the heading.


Net: fix the Zero7 clause — that one's on me — and this is good to merge. Rust and CI claims here are source review; no cargo or rustc available in my environment, so the account_load_cold_oog reasoning is from reading the test, not running it.

@crazywriter1

Copy link
Copy Markdown
Contributor Author

Thanks and fair catch on Zero7. I followed your earlier wording without tracing the gate; you're right that account_load_cost isn't hardfork-gated and the cold OOG test already shows that under Zero6. @osr21

Pushed a fix:

  • Dropped the Zero7 activation claim. Pricing is live from the v0.7.0 binary; Zero7 only gates CallFrom existing.
  • Old/New sub-bullets so the heading isn't repeated.
  • [EVM] sorted between [Config] and [Format].

@osr21

osr21 commented Sep 3, 2026

Copy link
Copy Markdown

Checked 0a8fcf1f. The Zero7 retraction landed correctly, and I verified the two historical claims rather than assuming them this time. One substantive issue remains, and it's the other half of my bad steer — the example list names the wrong paths.

Verified correct

The Old (v0.6.x) claim is precisely true. I checked out the tag rather than trusting the framing. v0.6.0's helpers.rs already priced storage via COLD_SLOAD_COST / WARM_STORAGE_READ_COST in read/write, and its transfer / balance_incr / balance_decr already called load_account — but charged nothing for it. No account_load_cost, no COLD_ACCOUNT_ACCESS_COST anywhere in the file. So "did not charge EIP-2929 cold/warm account-access gas for loaded accounts" is exactly right, and the qualifier is load-bearing — a looser "didn't implement EIP-2929" would have been false.

[EVM] ordering and the Old/New restructure are both resolved.

The no-hardfork-gate point is stronger than stated. Mainnet launched at Zero6 with Zero7/Zero8 activating later by timestamp (ARC_ZERO7_HARDFORK_TIMESTAMP_ACTIVATION_MAINNET), while testnet has Zero7 active at timestamp 0. So on mainnet there is a real window where the binary is v0.7.x and Zero7 is not active — which is precisely when "re-estimate at upgrade time, not at the next fork" matters.

The example list names the Zero7-gated paths, not the repriced ones

This one is mine too — I suggested Memo / Multicall3From / CallFrom as replacements for TokenMessenger. They're the right in-tree names, but they are not the paths account_load_cost reaches.

account_load_cost has exactly four call sites, all in helpers.rstransfer (:396, :414), balance_incr (:470), balance_decr (:517). Those three helpers are called from exactly one place: native_coin_authority.rs (:177 mint, :243 burn, :333 transfer). I grepped call_from.rs and subcall.rs directly: zero matches for account_load_cost, COLD_ACCOUNT_ACCESS_COST, or load_account.

The CallFrom cold-access charge is a different mechanism in a different cratecrates/evm/src/evm.rs, where the subcall overhead is abi_decode_gas + COLD_ACCOUNT_ACCESS_COST (:184, :189, and the init_subcall_gas_overhead tests around :7520). That's CHANGELOG L217, filed under Fixes. The account-load repricing is L182, under Changes. Two separate changes that happen to share the EIP-2929 heading.

That matters because the two have opposite upgrade profiles:

mechanism gated? existed in v0.6.0?
Native fiat token mint/burn/transfer account_load_cost (helpers.rs) no — ships with the binary yes, and was cheaper
Memo / Multicall3From / CallFrom subcall overhead (evm.rs) yes — Zero7 precompile inert pre-Zero7

So the third sub-bullet warns about stale v0.6.x estimates for the one set of paths where, on mainnet, no usable v0.6.x estimate can existCallFrom isn't registered until Zero7, so those calls don't succeed to be estimated. Meanwhile it omits the only path account_load_cost actually reaches.

And that path is not niche. NATIVE_COIN_AUTHORITY_ADDRESS's sole permitted caller is NATIVE_FIAT_TOKEN_ADDRESS (native_coin_authority.rs:44, enforced at :120, :212, :300). Every native fiat token mint, burn, and transfer routes through it. Those worked on v0.6.0, they reprice the moment the binary becomes v0.7.x, and there's no fork to wait for — the exact scenario the entry exists to flag.

Suggested amendment to the third sub-bullet:

  • eth_estimateGas results and hardcoded gas limits taken against a v0.6.x node can be too low. This applies chiefly to native fiat token mint / burn / transfer, which route through the NativeCoinAuthority precompile and are not hardfork-gated. CallFrom subcalls (Memo, Multicall3From) also charge cold account access, but that precompile only exists once Zero7 is active.

That keeps your in-tree names, drops the implication that Memo/Multicall3From estimates are the migration risk, and puts the ungated path first.

Separately: v0.7.0 was never published

Worth flagging given this PR exists to fix a dangling link. There is no v0.7.0 tag and no v0.7.0 release — the published set is v0.6.0, v0.7.1, v0.7.2, v0.7.3, v0.8.0. Both links in the file's own v0.7.0 header (BREAKING_CHANGES.md:89) currently 404:

GET /repos/circlefin/arc-node/compare/v0.6.0...v0.7.0   -> 404
GET /repos/circlefin/arc-node/releases/tags/v0.7.0      -> 404

The header disclaimer ("links resolve once the corresponding tag is published") covers a pending tag, but every later tag is already out, so v0.7.0 reads as skipped rather than pending. Pre-existing and out of scope for this PR — except for one line it adds: "Re-estimate against a v0.7.0 (or later) node" points operators at a version they cannot obtain. Upgrading from v0.6.0, the first available target is v0.7.1. Suggest "against v0.7.1 or later" and leaving the section heading alone.


With the third sub-bullet reworded this is accurate and I'd consider it done. Usual caveat: no cargo or rustc available here, so all Rust conclusions are source review plus the repo's own tests — the tag comparisons are real git show against v0.6.0, and the 404s are live API calls.

@crazywriter1

Copy link
Copy Markdown
Contributor Author

Checked 0a8fcf1f. The Zero7 retraction landed correctly, and I verified the two historical claims rather than assuming them this time. One substantive issue remains, and it's the other half of my bad steer — the example list names the wrong paths.

Verified correct

The Old (v0.6.x) claim is precisely true. I checked out the tag rather than trusting the framing. v0.6.0's helpers.rs already priced storage via COLD_SLOAD_COST / WARM_STORAGE_READ_COST in read/write, and its transfer / balance_incr / balance_decr already called load_account — but charged nothing for it. No account_load_cost, no COLD_ACCOUNT_ACCESS_COST anywhere in the file. So "did not charge EIP-2929 cold/warm account-access gas for loaded accounts" is exactly right, and the qualifier is load-bearing — a looser "didn't implement EIP-2929" would have been false.

[EVM] ordering and the Old/New restructure are both resolved.

The no-hardfork-gate point is stronger than stated. Mainnet launched at Zero6 with Zero7/Zero8 activating later by timestamp (ARC_ZERO7_HARDFORK_TIMESTAMP_ACTIVATION_MAINNET), while testnet has Zero7 active at timestamp 0. So on mainnet there is a real window where the binary is v0.7.x and Zero7 is not active — which is precisely when "re-estimate at upgrade time, not at the next fork" matters.

The example list names the Zero7-gated paths, not the repriced ones

This one is mine too — I suggested Memo / Multicall3From / CallFrom as replacements for TokenMessenger. They're the right in-tree names, but they are not the paths account_load_cost reaches.

account_load_cost has exactly four call sites, all in helpers.rstransfer (:396, :414), balance_incr (:470), balance_decr (:517). Those three helpers are called from exactly one place: native_coin_authority.rs (:177 mint, :243 burn, :333 transfer). I grepped call_from.rs and subcall.rs directly: zero matches for account_load_cost, COLD_ACCOUNT_ACCESS_COST, or load_account.

The CallFrom cold-access charge is a different mechanism in a different cratecrates/evm/src/evm.rs, where the subcall overhead is abi_decode_gas + COLD_ACCOUNT_ACCESS_COST (:184, :189, and the init_subcall_gas_overhead tests around :7520). That's CHANGELOG L217, filed under Fixes. The account-load repricing is L182, under Changes. Two separate changes that happen to share the EIP-2929 heading.

That matters because the two have opposite upgrade profiles:

mechanism gated? existed in v0.6.0?
Native fiat token mint/burn/transfer account_load_cost (helpers.rs) no — ships with the binary yes, and was cheaper
Memo / Multicall3From / CallFrom subcall overhead (evm.rs) yes — Zero7 precompile inert pre-Zero7
So the third sub-bullet warns about stale v0.6.x estimates for the one set of paths where, on mainnet, no usable v0.6.x estimate can existCallFrom isn't registered until Zero7, so those calls don't succeed to be estimated. Meanwhile it omits the only path account_load_cost actually reaches.

And that path is not niche. NATIVE_COIN_AUTHORITY_ADDRESS's sole permitted caller is NATIVE_FIAT_TOKEN_ADDRESS (native_coin_authority.rs:44, enforced at :120, :212, :300). Every native fiat token mint, burn, and transfer routes through it. Those worked on v0.6.0, they reprice the moment the binary becomes v0.7.x, and there's no fork to wait for — the exact scenario the entry exists to flag.

Suggested amendment to the third sub-bullet:

  • eth_estimateGas results and hardcoded gas limits taken against a v0.6.x node can be too low. This applies chiefly to native fiat token mint / burn / transfer, which route through the NativeCoinAuthority precompile and are not hardfork-gated. CallFrom subcalls (Memo, Multicall3From) also charge cold account access, but that precompile only exists once Zero7 is active.

That keeps your in-tree names, drops the implication that Memo/Multicall3From estimates are the migration risk, and puts the ungated path first.

Separately: v0.7.0 was never published

Worth flagging given this PR exists to fix a dangling link. There is no v0.7.0 tag and no v0.7.0 release — the published set is v0.6.0, v0.7.1, v0.7.2, v0.7.3, v0.8.0. Both links in the file's own v0.7.0 header (BREAKING_CHANGES.md:89) currently 404:

GET /repos/circlefin/arc-node/compare/v0.6.0...v0.7.0   -> 404
GET /repos/circlefin/arc-node/releases/tags/v0.7.0      -> 404

The header disclaimer ("links resolve once the corresponding tag is published") covers a pending tag, but every later tag is already out, so v0.7.0 reads as skipped rather than pending. Pre-existing and out of scope for this PR — except for one line it adds: "Re-estimate against a v0.7.0 (or later) node" points operators at a version they cannot obtain. Upgrading from v0.6.0, the first available target is v0.7.1. Suggest "against v0.7.1 or later" and leaving the section heading alone.

With the third sub-bullet reworded this is accurate and I'd consider it done. Usual caveat: no cargo or rustc available here, so all Rust conclusions are source review plus the repo's own tests — the tag comparisons are real git show against v0.6.0, and the 404s are live API calls.

Fair. account_load_cost only hits NativeCoinAuthority mint/burn/transfer; CallFrom is a separate Zero7-gated charge. Updated the example list and re-estimate target to v0.7.1 (no v0.7.0 tag).

@osr21

osr21 commented Sep 4, 2026

Copy link
Copy Markdown

Disclosure: I'm not affiliated with Circle — an external community contributor, not a maintainer, with no write access to this repository. Anything I say here is advisory only; please defer to Circle maintainers for the binding review. Rust findings are source-review only (no cargo/rustc available); tag comparisons below are real git show against the published tags.


Checked 4808dc9. The example list and the v0.7.1 target both landed correctly. But re-reading the result against the source rather than against my own previous comment, I over-corrected you in the last round and the merged sentence is now wrong. Retracting that below.

Verified correct in this revision

  • The second and third sub-bullets now name NativeCoinAuthority mint / burn / transfer, which matches the four account_load_cost call sites in helpers.rs (:396, :414, :470, :517).
  • I re-checked the one call site I asserted from memory last time: native_coin_authority.rs:333 does call transfer(...), alongside :177 balance_incr and :243 balance_decr. That claim holds.
  • The CallFrom / Zero7 statement is right, and I confirmed it from the tests rather than inferring it — evm.rs:3473 and :3489 assert "CallFrom should be registered when Zero7 is active" / "should not be registered when Zero7 is inactive". Registration really is fork-gated, so "only exists once Zero7 is active" is accurate.
  • [EVM] in the legend is alphabetically placed and the anchor still resolves.

Retraction: the account-load pricing is hardfork-gated in v0.7.x — on Zero6

I pushed you to drop the Zero7 gate in 0a8fcf1f. Zero7 was indeed the wrong fork, but "not gated behind a hardfork" is not the right replacement, and that is now stated with emphasis in the file:

Note this is not gated behind a hardfork — the new pricing takes effect as soon as the node runs v0.7.1 or later…

In the binary that entry points operators at, the charge is conditional. From git show v0.7.1:crates/precompiles/src/helpers.rs:

/// Under Zero6+, applies EIP-2929 warm/cold pricing. Before Zero6, a flat
/// cost is charged (matches pre-hardfork behavior for the `balance_incr`,
/// `balance_decr` and `transfer` helpers).
fn account_load_cost(is_cold: bool, hardfork_flags: ArcHardforkFlags) -> u64 {
    if hardfork_flags.is_active(ArcHardfork::Zero6) {
        if is_cold { COLD_ACCOUNT_ACCESS_COST } else { WARM_STORAGE_READ_COST }
    } else {
        PRECOMPILE_SLOAD_GAS_COST
    }
}

The gate is present in v0.7.1 and v0.7.2. It is removed in v0.8.0, where the signature becomes fn account_load_cost(is_cold: bool) -> u64 and the pricing is unconditional — which is the form on main today, and I suspect is why it reads as ungated when you check current source. So the sentence is accurate for v0.8.0, and wrong for exactly the versions the v0.7.0 section is about.

Why the operator guidance still survives

Worth being clear that this is a mechanism error, not bad advice — the conclusion holds, for a reason the current wording doesn't give:

  • Mainnet: ARC_MAINNET_HARDFORKS sets Zero6 => ForkCondition::Block(0) (hardforks.rs), so Zero6 is active from genesis. The gate is always satisfied, and the reprice does land the moment the binary becomes v0.7.1.
  • Testnet: Zero5/Zero6 activated at timestamp 1779894517 = 2026-05-27 15:08:37 UTC, three months ago. Also always satisfied now.

So "re-estimate at upgrade time rather than at the next fork" is the correct instruction today on both live networks. The problem is that the file explains it by denying the gate exists, rather than by noting the gate is already open.

That distinction wasn't always cosmetic. CHANGELOG.md:117 records the window where the current wording would have been actively misleading:

Note: testnet node operators must use v0.7.1 before timestamp 1779894517 … when Zero5/Zero6 activate on testnet.

A testnet node upgraded to v0.7.1 before that date charged the flat PRECOMPILE_SLOAD_GAS_COST and repriced at the fork, not at the upgrade — the precise opposite of what the entry now asserts. Devnet still activates Zero6 by block (ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET = 40033853), and any chain assembled from BASE_FORKS has to schedule it explicitly, so the ungated framing doesn't generalize.

Suggested amendment to the fourth sub-bullet

  • Re-estimate against a v0.7.1 (or later) node. Transaction outcomes are unchanged for callers that already supply sufficient gas. The new pricing is not waiting on a future fork: it is gated on Zero6, which is already active on all live networks (mainnet from block 0, testnet since 2026-05-27), so it takes effect as soon as the node runs v0.7.1 or later — re-estimate at upgrade time rather than at the next fork. (v0.8.0 drops the gate entirely and prices account loads unconditionally. CallFrom itself activates with Zero7, but that governs the precompile's availability, not the NativeCoinAuthority account-load pricing.)

That keeps your operational conclusion and the CallFrom parenthetical intact, and replaces the one clause I got wrong.

Two smaller things

  1. The v0.8.0 gate removal is itself undocumented. The v0.8.0 changelog section has no entry for it — the only nearby line is the unrelated [EL] Gate corrected EIP-161 deletion … behind Zero8. Since Zero6 is active everywhere, dropping the gate is a behavioural no-op in practice, so I would not hold this PR for it; noting it only because this PR exists to close exactly this kind of documentation gap, and a reader comparing v0.7.x and v0.8.0 source will find the mechanics changed with nothing recording it.
  2. Backtick consistency. The revision dropped code formatting on NativeCoinAuthority and on mint / burn / transfer in the second and third sub-bullets, while sibling entries in this file consistently mark identifiers and flags. Purely cosmetic.

Apologies for the extra round — the Zero7 correction was right, but I should have checked what the gate actually was before telling you there wasn't one. With the fourth sub-bullet adjusted I think this is accurate and done.

@crazywriter1

crazywriter1 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Updated. Fourth bullet now says the NativeCoinAuthority account-load charge is Zero6-gated (already open on live nets), so re-estimate still lands at the v0.7.1+ upgrade. Also restored backticks on NativeCoinAuthority / mint / burn / transfer. Left the v0.8.0 gate-removal note out of this PR. @osr21

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants